package qq2564874169.tuaotuao.fx.context;

import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import qq2564874169.tuaotuao.fx.Func1T;
import qq2564874169.tuaotuao.fx.FxStream;

import java.lang.annotation.Annotation;
import java.util.List;

public class FxSpringContext extends GenericApplicationContext implements BeanContext {
    public final SpringContextExtend extend = new SpringContextExtend(this);

    public FxSpringContext() {

    }

    @Override
    public void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
        extend.onRemoveBeanDef(beanName);
        super.removeBeanDefinition(beanName);
    }

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
        extend.onNewBeanDef(beanName, beanDefinition);
        super.registerBeanDefinition(beanName, beanDefinition);
    }

    public void addDefaultScope(String pkg, String scope) {
        extend.addDefaultScope(new DefaultScopeSetting(pkg, scope));
    }

    public FxSpringContext child() {
        FxSpringContext ctx = new FxSpringContext();
        ctx.setParent(this);
        extend.addChild(ctx.extend);
        ctx.refresh();
        return ctx;
    }

    @Override
    public void registerBeanClass(Class<?> beanClass) {
        registerBean(beanClass);
    }

    @Override
    public <T> List<Class<T>> getClassListForType(Class<T> clazz) {
        return extend.getClassListForType(clazz);
    }

    public <T> void setBean(T bean) {
        extend.setBean(bean.getClass(), bean);
    }

    public void scanPackage(String... pkg) {
        scanPackage(null, pkg);
    }

    public void scanPackage(Func1T<String, Boolean> filter, String... pkg) {
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                return beanDefinition.getMetadata().isIndependent();
            }
        };
        if (filter != null) {
            scanner.addIncludeFilter((r, f) -> {
                var name = r.getClassMetadata().getClassName();
                return filter.execute(name);
            });
        }
        scanner.scan(pkg);
    }


    @Override
    public String[] getBeanNamesForType(Class<?> type) {
        String[] names = super.getBeanNamesForType(type);
        if (names.length == 0 && getParent() != null && getParent() != this) {
            names = getParent().getBeanNamesForType(type);
        }
        return names;
    }

    @Override
    public String[] getBeanNamesForType(ResolvableType type) {
        String[] names = super.getBeanNamesForType(type);
        if (names.length == 0 && getParent() != null && getParent() != this) {
            names = getParent().getBeanNamesForType(type);
        }
        return names;
    }

    @Override
    public String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
        String[] names = super.getBeanNamesForType(type, includeNonSingletons, allowEagerInit);
        if (names.length == 0 && getParent() != null && getParent() != this) {
            names = getParent().getBeanNamesForType(type, includeNonSingletons, allowEagerInit);
        }
        return names;
    }

    @Override
    public String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType) {
        String[] names = super.getBeanNamesForAnnotation(annotationType);
        if (names.length == 0 && getParent() != null && getParent() != this) {
            names = getParent().getBeanNamesForAnnotation(annotationType);
        }
        return names;
    }

    @Override
    public String[] getBeanDefinitionNames() {
        ApplicationContext ctx = this;
        var sm = FxStream.create(super.getBeanDefinitionNames());
        while (ctx.getParent() != null) {
            sm.add(ctx.getParent().getBeanDefinitionNames());
            ctx = ctx.getParent();
        }
        return sm.distinct().toArray(String.class);
    }
}
